﻿/*
   Copyright 2012 Dmitry Bratus

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AopAlliance.Intercept;
using Flower.Logging;
using Flower.Services.Data;
using System.Transactions;

namespace Flower.Directory.Host.Default.Advices
{
    internal class Logging : IMethodInterceptor
    {
        private const string IND = "  ";
        private const string FIELD_IND = IND + IND;
        private const string NULL = "null";
        private const string BLOB = "BLOB";

        #region DirectoryEntryRenderer

        private class DirectoryEntryRenderer : IDirectoryEntryVisitor
        {
            private readonly StringBuilder _builder = new StringBuilder();

            public static string Render(DirectoryEntry entry)
            {
                var renderer = new DirectoryEntryRenderer();
                entry.AcceptVisitor(renderer);
                return renderer._builder.ToString();
            }

            public static string Render(IEnumerable<DirectoryEntry> entries)
            {
                var builder = new StringBuilder();

                foreach (DirectoryEntry entry in entries)
                {
                    builder.AppendLine(IND + Render(entry));
                }

                return builder.ToString();
            }

            private static string Render(IEnumerable<AccessControlEntry> acl)
            {
                return
                    string.Concat
                    (
                        "{ ",
                        string.Join("; ", acl.Select(ent => string.Format("{0}: {1}", ent.Role, ent.Permission))),
                        " }"
                    );
            }

            private void Render(string entryType, DirectoryEntry entry, Action renderFields)
            {
                _builder.AppendFormat("{0} {{\n", entryType);
                _builder.AppendFormat(FIELD_IND + "Id = {0}\n", entry.Id ?? NULL);
                _builder.AppendFormat(FIELD_IND + "Name = {0}\n", entry.Name ?? NULL);
                _builder.AppendFormat(FIELD_IND + "CreatedOn = {0}\n", entry.CreatedOn);
                _builder.AppendFormat(FIELD_IND + "UpdatedOn = {0}\n", entry.UpdatedOn);
                _builder.AppendFormat(FIELD_IND + "AccessControlList = {0}\n", Render(entry.AccessControlList));

                renderFields();

                _builder.Append(IND + "}}");
            }

            public void Visit(Assembly assembly)
            {
                Render
                (
                    "Assembly", 
                    assembly, 
                    () => _builder.AppendFormat(FIELD_IND + "Image = {0}\n", (assembly.Image != null) ? BLOB : NULL)
                );
            }

            public void Visit(Folder folder)
            {
                Render("Folder", folder, () => { });
            }

            public void Visit(Link link)
            {
                Render
                (
                    "Link", 
                    link,
                    () => _builder.AppendFormat(FIELD_IND + "Target = {0}\n", link.Target ?? NULL)
                );
            }

            public void Visit(Message message)
            {
                Render
                (
                    "Message",
                    message,
                    () => _builder.AppendFormat(FIELD_IND + "Data = {0}\n", (message.Data != null) ? BLOB : NULL)
                );
            }

            public void Visit(Process process)
            {
                Render
                (
                    "Process",
                    process,
                    () => 
                        _builder.AppendFormat(FIELD_IND + "Workflow = {0}\n", process.Workflow ?? NULL)
                );
            }

            public void Visit(Processor processor)
            {
                Render
                (
                    "Processor",
                    processor,
                    () => _builder.AppendFormat(FIELD_IND + "CallbackEndpointConfiguration = {0}\n", (processor.Configuration != null) ? BLOB : NULL)
                );
            }

            public void Visit(Role role)
            {
                Render
                (
                    "Role",
                    role,
                    () => _builder.AppendFormat(FIELD_IND + "Users = {0}\n", (role.Users != null) ? string.Join(", ", role.Users) : NULL)
                );
            }

            public void Visit(ServiceContainer serviceContainer)
            {
                Render
                (
                    "ServiceContainer",
                    serviceContainer,
                    () => _builder.AppendFormat(FIELD_IND + "Configuration = {0}\n", (serviceContainer.Configuration != null) ? BLOB : NULL)
                );
            }

            public void Visit(Set set)
            {
                Render
                (
                    "Set",
                    set,
                    () =>
                    {
                        _builder.AppendFormat(FIELD_IND + "MessageType = {0}\n", set.MessageType ?? NULL);
                        _builder.AppendFormat(FIELD_IND + "Capacity = {0}\n", set.Capacity);
                    }
                );
            }

            public void Visit(State state)
            {
                Render
                (
                    "State",
                    state,
                    () => _builder.AppendFormat(FIELD_IND + "Data = {0}\n", (state.Data != null) ? BLOB : NULL)
                );
            }

            public void Visit(Workflow workflow)
            {
                Render
                (
                    "Workflow",
                    workflow,
                    () =>
                    {
                        _builder.AppendFormat(FIELD_IND + "DefinitionType = {0}\n", workflow.DefinitionType ?? NULL);
                        _builder.AppendFormat(FIELD_IND + "PreferredProcessors = {0}\n", (workflow.PreferredProcessors != null) ? string.Join(", ", workflow.PreferredProcessors) : NULL);
                    }
                );
            }

            public void Visit(DirectoryEntryInfo directoryEntryInfo)
            {
                Render
                (
                    "DirectoryEntryInfo",
                    directoryEntryInfo,
                    () =>
                        _builder.AppendFormat(FIELD_IND + "Type = {0}\n", directoryEntryInfo.Type)
                );
            }
        }

        #endregion

        private static readonly Dictionary<string, Func<IMethodInvocation, string>> InArgsRenderers =
            new Dictionary<string, Func<IMethodInvocation, string>> 
            {
                { "GetPathById", inv => string.Format(IND + "id: {0}\n", inv.Arguments) },
                { "Get", inv => string.Format(IND + "what: {0}\n", inv.Arguments) },
                { "GetInfo", inv => string.Format(IND + "what: {0}\n", inv.Arguments) },
                { 
                    "GetChildren", 
                    inv => 
                        string.Format
                        (
                            IND + "parent: {0}\n" + 
                            IND + "entryTypes: {1}\n" + 
                            IND + "property: {2}\n" + 
                            IND + "order: {3}\n" + 
                            IND + "offset: {4}\n" + 
                            IND + "limit: {5}\n",
                            inv.Arguments
                        ) 
                },
                { 
                    "GetChildrenInfo", 
                    inv => 
                        string.Format
                        (
                            IND + "parent: {0}\n" + 
                            IND + "entryTypes: {1}\n" + 
                            IND + "property: {2}\n" + 
                            IND + "order: {3}\n" + 
                            IND + "offset: {4}\n" + 
                            IND + "limit: {5}\n",
                            inv.Arguments
                        ) 
                },
                { 
                    "CountChildren", 
                    inv => 
                        string.Format
                        (
                            IND + "parent: {0}\n" + 
                            IND + "entryTypes: {1}\n", 
                            inv.Arguments
                        ) 
                }, 
                {
                    "GetAncestor", 
                    inv => 
                        string.Format
                        (
                            IND + "child: {0}\n" + 
                            IND + "n: {1}\n" + 
                            IND + "ancestorEntryTypes: {2}\n",
                            inv.Arguments
                        ) 
                }, 
                {
                    "GetAncestorInfo", 
                    inv => 
                        string.Format
                        (
                            IND + "child: {0}\n" + 
                            IND + "n: {1}\n" + 
                            IND + "ancestorEntryTypes: {2}\n",
                            inv.Arguments
                        ) 
                }, 
                {
                    "Find", 
                    inv => 
                        string.Format
                        (
                            IND + "what: {0}\n" + 
                            IND + "entryTypes: {1}\n", 
                            inv.Arguments
                        ) 
                }, 
                {
                    "Save", 
                    inv => 
                        string.Format
                        (
                            IND + "parent: {0}\n" + 
                            IND + "entry: {1}\n", 
                            inv.Arguments[0],
                            DirectoryEntryRenderer.Render((DirectoryEntry)inv.Arguments[1])
                        ) 
                }, 
                {
                    "Move", 
                    inv => 
                        string.Format
                        (
                            IND + "what: {0}\n" + 
                            IND + "where: {1}\n", 
                            inv.Arguments
                        ) 
                },
                { "Remove", inv => string.Format(IND + "what: {0}\n", inv.Arguments) }, 
                {
                    "Exists", 
                    inv => 
                        string.Format
                        (
                            IND + "what: {0}\n" + 
                            IND + "timestamp: {1}\n", 
                            inv.Arguments
                        ) 
                },
                { "ExecuteScript", inv => string.Empty }
            };

        private static readonly Dictionary<string, Func<object, string>> RetvalsRenderers =
            new Dictionary<string, Func<object, string>>
            {
                { "GetPathById", retval => retval.ToString() },
                { "Get", retval => (retval != null) ? "\n" + IND + DirectoryEntryRenderer.Render((DirectoryEntry)retval) : "null" },
                { "GetInfo", retval => (retval != null) ? "\n" + IND + DirectoryEntryRenderer.Render((DirectoryEntry)retval) : "null" },
                { "GetChildren", retval => "[\n" + DirectoryEntryRenderer.Render((IEnumerable<DirectoryEntry>)retval) + "]\n" },
                { "GetChildrenInfo", retval => "[\n" + DirectoryEntryRenderer.Render((IEnumerable<DirectoryEntry>)retval) + "]\n" },
                { "CountChildren", retval => retval.ToString() },
                { "GetAncestor", retval => (retval != null) ? "\n" + IND + DirectoryEntryRenderer.Render((DirectoryEntry)retval) : "null" },
                { "GetAncestorInfo", retval => (retval != null) ? "\n" + IND + DirectoryEntryRenderer.Render((DirectoryEntry)retval) : "null" },
                { "Find", retval => "[\n" + DirectoryEntryRenderer.Render((IEnumerable<DirectoryEntry>)retval) + "]\n" },
                { "Save", retval => retval.ToString() },
                { "Move", retval => null },
                { "Remove", retval => null },
                { "Exists", retval => retval.ToString() },
                { "ExecuteScript", retval => null }
            };

        private readonly Log _log;

        public Logging(string source)
        {
            _log = LogManager.CreateLog(source);
        }

        private string GetTransactionInfo()
        {
            if (Transaction.Current != null)
            {
                return string.Format("Transaction {0} {1}.", Transaction.Current.TransactionInformation.LocalIdentifier, Transaction.Current.IsolationLevel);
            }
            else
            {
                return "No transaction";
            }
        }

        public object Invoke(IMethodInvocation invocation)
        {
            string methodName = invocation.Method.Name;
            Func<object, string> retValRenderer;
            Func<IMethodInvocation, string> inArgsRenderer;

            if (!InArgsRenderers.TryGetValue(methodName, out inArgsRenderer))
            {
                return invocation.Proceed();
            }

            if (!RetvalsRenderers.TryGetValue(methodName, out retValRenderer))
            {
                return invocation.Proceed();
            }

            _log.Trace
            (
                () => 
                    string.Format
                    (
                        "Executing method {0}\n{1}\n{0}", 
                        methodName,
                        GetTransactionInfo(),
                        inArgsRenderer(invocation)
                    )
            );

            object retval;

            try
            {
                retval = invocation.Proceed();

                _log.Trace(() => 
                {
                    var retvalMsg = retValRenderer(retval);

                    return (retvalMsg != null) ?
                        string.Format("Method {0} returned {1}", methodName, retvalMsg) :
                        string.Format("Method {0} is finished", methodName);
                });
            }
            catch(Exception ex)
            {
                _log.Error(() => string.Format("Method {0} thrown exception", methodName), ex);
                throw;
            }

            return retval;
        }
    }
}
